package com.koi.job.service.job;

import cn.hutool.cron.CronUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.koi.common.constants.KoiConstants;
import com.koi.common.ex.koi.YUtil;
import com.koi.common.satoken.KoiLoginUtil;
import com.koi.job.task.CommonTimerTaskRunner;
import com.koi.plus.mapper.system.SysJobMapper;
import com.koi.pojo.system.SysJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;

/**
 * @author ･ᴗ･
 * @description [SysJobServiceImpl]
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implements SysJobService {

    private final SysJobMapper sysJobMapper;

    /**
     * @description [停止任务]
     * @author ･ᴗ･
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void pauseJob(Long id) {
        SysJob byId = this.getById(id);
        YUtil.isTrue(byId.getJobStatus().equals("1"), "该任务已处于停止状态");
        CronUtil.remove(id + "");
        LambdaUpdateWrapper<SysJob> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(SysJob::getJobStatus, "1");
        updateWrapper.eq(SysJob::getJobId, id);
        boolean update = this.update(null, updateWrapper);
        YUtil.isTrue(!update, "暂停任务失败，请重试");
    }

    /**
     * @description [启动定时任务]
     * @author ･ᴗ･
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void resumeJob(Long id) {
        YUtil.isTrue(null == id, "该任务未查询到");
        SysJob job = this.getById(id);
        // 先停止定时任务
        CronUtil.remove(id + "");
        // 再注册定时任务
        CronUtil.schedule(job.getJobId() + "", job.getCronExpression(), () -> {
            executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
        });
    }

    /**
     * @param classPath  类路径
     * @param methodName 方法名称
     * @param params     参数
     * @description [执行方法，多个参数传递，必须使用逗号分割，一个一个对应]
     * @author ･ᴗ･
     */
    private void executeMethod(String classPath, String methodName, String params) {
        YUtil.isTrue(StringUtils.isBlank(classPath), "类绝对路径不能为空");
        YUtil.isTrue(StringUtils.isBlank(methodName), "方法名称不能为空");
        // log.info("执行参数params：{}",params);
        /* 单个参数对应 */
        // try {
        //     // 动态地获取了 CommonTimerTaskRunner 接口中的自定义方法，并使用反射获取该方法
        //     Method method = CommonTimerTaskRunner.class.getMethod(methodName, String.class);
        //     // 使用SpringUtil获取类的实例，并调用带参数的方法
        //     CommonTimerTaskRunner runner = (CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(classPath));
        //     method.invoke(runner, params);
        // } catch (Exception e) {
        //     e.printStackTrace();
        //     throw new RuntimeException("定时任务找不到对应的类，名称为：" + methodName);
        // }

        /* 改造为多参数对应 */
        try {
            // 使用SpringUtil获取类的实例
            CommonTimerTaskRunner runner = (CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(classPath));

            Method[] methods = runner.getClass().getMethods();
            Method targetMethod = null;
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    if (parameterTypes.length > 0 && StringUtils.isBlank(params)) {
                        throw new IllegalArgumentException("缺少参数");
                    }
                    if (parameterTypes.length == 0 && StringUtils.isNotBlank(params)) {
                        throw new IllegalArgumentException("不应传递参数");
                    }
                    if (parameterTypes.length > 0 && StringUtils.isNotBlank(params)) {
                        String[] paramArray = params.split(",");
                        if (paramArray.length != parameterTypes.length) {
                            throw new IllegalArgumentException("参数数量不匹配");
                        }
                        for (int i = 0; i < paramArray.length; i++) {
                            Object convertedValue = convertToType(parameterTypes[i], paramArray[i]);
                            if (convertedValue == null) {
                                throw new IllegalArgumentException("参数类型不匹配");
                            }
                        }
                        targetMethod = method;
                    } else {
                        targetMethod = method;
                    }
                    break;
                }
            }

            if (targetMethod != null) {
                // 调用方法
                if (StringUtils.isNotBlank(params)) {
                    targetMethod.invoke(runner, extractTypedParams(targetMethod.getParameterTypes(), params));
                } else {
                    targetMethod.invoke(runner);
                }
            } else {
                throw new IllegalArgumentException("指定方法不存在");
            }
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("执行定时任务出现异常：" + e.getMessage());
        }
    }

    /**
     * @description [多参数使用逗号分隔开]
     * @author ･ᴗ･
     */
    private Object[] extractTypedParams(Class<?>[] parameterTypes, String params) {
        String[] paramArray = params.split(",");
        Object[] typedParams = new Object[paramArray.length];
        for (int i = 0; i < paramArray.length; i++) {
            typedParams[i] = convertToType(parameterTypes[i], paramArray[i]);
        }
        return typedParams;
    }

    /**
     * @description [类型转换]
     * @author ･ᴗ･
     */
    private Object convertToType(Class<?> targetType, String value) {
        if (targetType.equals(String.class)) { // String 类型
            return value;
        } else if (targetType.equals(Integer.class) || targetType.equals(int.class)) { // Integer 类型
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException ex) {
                return null;
            }
        } else if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) { // Boolean 类型
            return Boolean.parseBoolean(value);
        }
        // 根据需要添加更多类型转换
        return null;
    }

    /**
     * @description [任务调度状态修改]
     * @author ･ᴗ･
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateStatus(Long id, String jobStatus, String policyStatus) {
        YUtil.isTrue(StringUtils.isBlank(jobStatus) || id == null, "请传递相关信息");
        if (KoiConstants.ZERO_STRING.equals(jobStatus)) {
            if (KoiConstants.ONE_STRING.equals(policyStatus)) {
                resumeJob(id); // 启动定时任务
            }
        } else if (KoiConstants.ONE_STRING.equals(jobStatus)) {
            pauseJob(id); // 停止定时任务
        }
        LambdaUpdateWrapper<SysJob> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(SysJob::getJobStatus, jobStatus);
        wrapper.set(SysJob::getPolicyStatus, policyStatus);
        wrapper.eq(SysJob::getJobId, id);
        boolean update = this.update(null, wrapper);
        YUtil.isTrue(!update, "操作失败，请重试");
    }

    /**
     * @description [立即运行任务-执行一次]
     * @author ･ᴗ･
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void runNow(Long id) {
        SysJob job = this.getById(id);
        YUtil.isTrue(ObjectUtils.isEmpty(job) || job.getJobId() == null, "未查到当前任务");
        // 先停止定时任务
        CronUtil.remove(id + "");
        // 再执行一次定时任务
        executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
    }

    /**
     * @description [添加定时任务]
     * @author ･ᴗ･
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addJob(SysJob job) {
        // 1、参数校验
        checkParams(job);
        // 2、是否添加重复的定时任务
        LambdaQueryWrapper<SysJob> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysJob::getClassPath, job.getClassPath());
        wrapper.eq(SysJob::getMethodName, job.getMethodName());
        wrapper.eq(SysJob::getCronExpression, job.getCronExpression());
        long count = this.count(wrapper);
        YUtil.isTrue(count > 0, "存在重复执行的定时任务，名称为：" + job.getJobName());
        // 3、添加定时任务
        job.setCreateBy(KoiLoginUtil.getUserName());
        boolean save = this.save(job);
        YUtil.isTrue(!save, "添加失败，请重试");
        // 4、根据任务状态，进行执行定时策略
        if (KoiConstants.ZERO_STRING.equals(job.getJobStatus())) {
            if (KoiConstants.ONE_STRING.equals(job.getPolicyStatus())) {
                // 开启定时任务
                resumeJob(job.getJobId());
            } else if (KoiConstants.TWO_STRING.equals(job.getPolicyStatus())) {
                // 先停止定时任务
                CronUtil.remove(job.getJobId() + "");
                // 执行一次
                executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
            } else {
                // 停止任务
                CronUtil.remove(job.getJobId() + "");
                LambdaUpdateWrapper<SysJob> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.set(SysJob::getPolicyStatus, KoiConstants.THREE_STRING);
                updateWrapper.eq(SysJob::getJobId, job.getJobId());
                boolean update = this.update(updateWrapper);
                YUtil.isTrue(!update, "操作失败，请重试");
            }
        } else {
            // 停止任务，计划策略并改为放弃执行
            CronUtil.remove(job.getJobId() + "");
            LambdaUpdateWrapper<SysJob> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(SysJob::getPolicyStatus, KoiConstants.THREE_STRING);
            updateWrapper.eq(SysJob::getJobId, job.getJobId());
            boolean update = this.update(updateWrapper);
            YUtil.isTrue(!update, "操作失败，请重试");
        }
    }

    /**
     * @description [参数校验]
     * @author ･ᴗ･
     */
    private void checkParams(SysJob job) {
        // 校验表达式
        YUtil.isTrue(!CronExpression.isValidExpression(job.getCronExpression()), "cron表达式：" + job.getCronExpression() + "格式不正确");
        // 校验定时任务类
        try {
            Class<?> actionClass = Class.forName(job.getClassPath());
            YUtil.isTrue(!CommonTimerTaskRunner.class.isAssignableFrom(actionClass), "定时任务对应的类：" + job.getClassPath() + "不符合要求");
        } catch (ClassNotFoundException e) {
            YUtil.isTrue("定时任务找不到对应的类，名称为：" + job.getClassPath());
        }
    }

    /**
     * @description [修改定时任务]
     * @author ･ᴗ･
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateJob(SysJob job) {
        // 1、参数校验
        checkParams(job);
        YUtil.isTrue(job.getJobId() == null, "请选择需要修改的任务");
        // 2、是否修改为数据库已经存在的定时任务
        LambdaQueryWrapper<SysJob> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysJob::getClassPath, job.getClassPath());
        wrapper.eq(SysJob::getMethodName, job.getMethodName());
        SysJob sysJob = this.getOne(wrapper);
        YUtil.isTrue(ObjectUtils.isNotEmpty(sysJob) && !sysJob.getJobId().equals(job.getJobId()), "存在重复执行的定时任务，名称为：" + job.getJobName());
        // 3、修改任务
        job.setUpdateBy(KoiLoginUtil.getUserName());
        boolean update = this.updateById(job);
        YUtil.isTrue(!update, "修改失败，请重试");
        // 4、根据任务状态，进行执行定时策略
        if (KoiConstants.ZERO_STRING.equals(job.getJobStatus())) {
            if (KoiConstants.ONE_STRING.equals(job.getPolicyStatus())) {
                // 先停止定时任务，再开启定时任务
                resumeJob(job.getJobId());
            } else if (KoiConstants.TWO_STRING.equals(job.getPolicyStatus())) {
                // 先停止定时任务
                CronUtil.remove(job.getJobId() + "");
                // 再开始执行一次定时任务
                executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
            } else {
                // 停止任务，计划策略并改为放弃执行
                CronUtil.remove(job.getJobId() + "");
                LambdaUpdateWrapper<SysJob> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.set(SysJob::getPolicyStatus, KoiConstants.THREE_STRING);
                updateWrapper.eq(SysJob::getJobId, job.getJobId());
                boolean updateBoolean = this.update(updateWrapper);
                YUtil.isTrue(!updateBoolean, "操作失败，请重试");
            }
        } else {
            // 停止任务，计划策略并改为放弃执行
            CronUtil.remove(job.getJobId() + "");
            LambdaUpdateWrapper<SysJob> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(SysJob::getPolicyStatus, KoiConstants.THREE_STRING);
            updateWrapper.eq(SysJob::getJobId, job.getJobId());
            boolean updateBoolean = this.update(updateWrapper);
            YUtil.isTrue(!updateBoolean, "操作失败，请重试");
        }
    }

}
